package com.example.zz.example.pattern.hook;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.example.zz.example.R;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * @author: zhuozhang6
 * @date: 2022/5/29
 * @email: zhuozhang6@iflytek.com
 * @Description:
 * Hook是一种通过反射动态代理改变API执行结果的技术。能够将系统的API函数执行重定向（应用的触发事件和后台逻辑处理是根据事件流程一步步地向下执行。
 * 而Hook的意思，就是在事件传送到终点前截获并监控事件的传输，像个钩子钩上事件一样，并且能够在钩上事件时，处理一些自己特定的事件，例如逆向破解App）
 * Android 中的 Hook 机制，大致有两个方式：
 * 一、要 root 权限，直接 Hook 系统，可以干掉所有的 App。
 * 二、无 root 权限，但是只能 Hook 自身app，对系统其它 App 无能为力。
 * 插桩是以静态的方式修改第三方的代码，也就是从编译阶段，对源代码（中间代码）进行编译，而后重新打包，是静态的篡改；
 * 而Hook则不需要再编译阶段修改第三方的源码或中间代码，是在运行时通过反射的方式修改调用，是一种动态的篡改
 *
 */
public class HookActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hook);

        Button button = findViewById(R.id.hookClick);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("test", "点击事件继续");
            }
        });

        // Hook一下这个点击事件，增加点预处理
        // 1.跟踪下setOnClickListener方法都做了啥，关键的监听设置在哪里：
        // public void setOnClickListener(@Nullable OnClickListener l) {
        //        if (!isClickable()) {
        //            setClickable(true);
        //        }
        //        getListenerInfo().mOnClickListener = l;
        //    }
        // static class ListenerInfo {
        //        /**
        //         * Listener used to dispatch click events.
        //         * This field should be made private, so it is hidden from the SDK.
        //         * {@hide}
        //         */
        //        public OnClickListener mOnClickListener;
        // }
        // 1.1 所以我们最终要实现替换getListenerInfo()->ListenerInfo的mOnClickListener为我们自己代理的监听方法

        try {
            Method method = View.class.getDeclaredMethod("getListenerInfo");
            method.setAccessible(true);
            Log.e("test", "method=" + method.getName());
            // 获取Button的ListenerInfo对象mListenerInfo
            Object mListenerInfo = method.invoke(button);

            // 内部类需要使用$分隔
            Class<?> classListenerInfo = Class.forName("android.view.View$ListenerInfo");
            // 获取内部Field mOnClickListener
            Field field = classListenerInfo.getDeclaredField("mOnClickListener");
            // 然后获取Button的ListenerInfo对象mListenerInfo的mOnClickListener变量
            // --这就是真正的拿到了Button的监听回调View.OnClickListener的实例对象
            final View.OnClickListener onClickListener = (View.OnClickListener) field.get(mListenerInfo);

            // 然后准备替换为我们自己的点击事件
            // 1. 创建代理点击对象，然后替换 (这里继承接口实现一个类也可以)
            Object proxyOnClickListener = Proxy.newProxyInstance(this.getClassLoader(),
                    new Class[]{View.OnClickListener.class},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            Toast.makeText(HookActivity.this,
                                    "这是hook点击事件添加的toast：你点击我嘛，我很烦的！",
                                    Toast.LENGTH_SHORT).show();
                            // 为了保证其点击逻辑，除了插入我们的操作，我们还是要处理正常的调用逻辑
                            return method.invoke(onClickListener, args);
                        }
                    });

            // 2. 然后替换掉Button的点击事件
            field.set(mListenerInfo, proxyOnClickListener);
            // End.当点击的时候就会执行我们代理对象的invoke方法。然后你可以在invoke里面增加自己额外的操作。
            // --甚至你啥都不做，就这么让点击事件失效了，哈哈！
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}